package com.testing.web;

import com.google.common.io.Files;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import com.testing.DriverSelf.GoogleDriver;
import com.testing.DriverSelf.GoogleMobileDriver;
import com.testing.common.AutoTools;
import com.testing.common.MysqlUtils;
import com.testing.common.RobotUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
import sun.java2d.pipe.SpanIterator;

import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @Classname WebKeyword
 * @Description 类型说明
 * @Date 2022/10/7 20:12
 * @Created by 特斯汀Roy
 */
public class WebKeyword extends AutoTools {

    private  WebDriver driver;

    public  WebDriver getDriver() {
        return driver;
    }

    public void setDriver(WebDriver driver) {
        this.driver = driver;
    }

    public boolean openBrowser(String browserType) {
        try {
            switch (browserType) {
                case "firefox":
                    System.setProperty("webdriver.gecko.driver", "DriverExe\\geckodriver.exe");
                    driver = new FirefoxDriver();
                    break;
                case "ie":
                    System.setProperty("webdriver.ie.driver", "DriverExe\\IEDriverServer.exe");
                    driver = new InternetExplorerDriver();
                    break;
                case "edge":
                    System.setProperty("webdriver.edge.driver", "DriverExe\\msedgeDriver.exe");
                    driver = new EdgeDriver();
                    break;
                case "mobile":
                    GoogleMobileDriver gm = new GoogleMobileDriver("DriverExe\\chromedriver.exe");
                    driver = gm.getDriver();
                    break;
                case "newChrome":
                    System.setProperty("webdriver.chrome.driver", "DriverExe\\chromedriver.exe");
                    driver = new ChromeDriver();
                    break;
                case "chrome":
                default:
                    GoogleDriver gg = new GoogleDriver("DriverExe\\chromedriver.exe");
                    driver = gg.getDriver();
            }
            setBrowserSize();
            //隐式等待设置，加一次就够了。
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
            return true;
        } catch (Exception e) {
            log.error("启动浏览器失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 访问网站的url地址。
     *
     * @param url
     */
    public boolean visitWeb(String url) {
        url=useParam(url);
        AutoTools.log.info("正在访问" + url);
        driver.get(url);
        return true;
    }

    //调整浏览器的大小
    public void setBrowserSize() {
        Point point = new Point(280, 20);
        driver.manage().window().setPosition(point);
        Dimension range = new Dimension(1622, 1000);
        driver.manage().window().setSize(range);
    }

    /****************************元素定位与基本操作*************************************/
    /**
     * 通过css选择器定位元素并且输入
     *
     * @param css
     * @param content
     */
    public boolean inputByCss(String css, String content) {
        try {
            WebElement element = driver.findElement(By.cssSelector(css));
            element.clear();
            element.sendKeys(content);
            return true;
        } catch (Exception e) {
            log.error("通过css定位" + css + "元素失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 默认使用xpath定位并输入。
     *
     * @param xpath
     * @param content
     */
    public boolean input(String xpath, String content) {
        xpath = useParam(xpath);
        content = useParam(content);
        try {
            WebElement element = driver.findElement(By.xpath(xpath));
            element.clear();
            element.sendKeys(content);
            AutoTools.log.info("正在向" + xpath + "元素中输入" + content);
            return true;
        } catch (Exception e) {
            AutoTools.log.error("输入内容失败", e.fillInStackTrace());
            takeScreenShot("输入失败");
            return false;
        }
    }

    /**
     * 通过id进行点击
     *
     * @param id
     */
    public void clickByid(String id) {
        driver.findElement(By.id(id)).click();
    }

    /**
     * 对表单中的输入框直接提交数据
     *
     * @param css
     */
    public boolean submit(String css) {
        try {
            driver.findElement(By.cssSelector(css)).submit();
            return true;
        } catch (Exception e) {
            log.error("通过输入框直接submit失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 通用定位点击方法。
     */
    public void clickType(String type, String locator) {
        switch (type) {
            case "id":
                driver.findElement(By.id(locator)).click();
                break;
            case "css":
                driver.findElement(By.cssSelector(locator)).click();
                break;
            case "xpath":
                driver.findElement(By.xpath(locator)).click();
                break;
            default:
                AutoTools.log.info("不支持该定位方法。");
        }
    }

    /***
     * 定位的时候，根据定位表达式的特征来进行基本判断。
     * @param locator
     */
    public void clickAll(String locator) {
        //以/开头就是xpath定位
        if (locator.startsWith("/")) {
            driver.findElement(By.xpath(locator)).click();
        } else if (locator.startsWith("#") || locator.startsWith(".") || locator.contains("[")) {
            driver.findElement(By.cssSelector(locator)).click();
        } else {
            driver.findElement(By.id(locator)).click();
        }
    }

    /*******************************robot操作底层键鼠*************************************/
    /**
     * 默认使用xpath方法进行click操作。
     *
     * @param xpath
     */
    public boolean click(String xpath) {
        xpath = useParam(xpath);
        try {
            driver.findElement(By.xpath(xpath)).click();
            return true;
        } catch (Exception e) {
            AutoTools.log.error("点击" + xpath + "元素失败", e.fillInStackTrace());
            takeScreenShot("点击失败");
            return false;
        }
    }

    /**
     * 通过robot进行鼠标移动点击操作。
     *
     * @param x
     * @param y
     */
    public void rbClick(String x, String y) {
        try {
            RobotUtils.rbClick(Integer.parseInt(x), Integer.parseInt(y));
        } catch (NumberFormatException exception) {
            exception.printStackTrace();
            AutoTools.log.info("输入的不是数字。");
        }
    }

    /**
     * 通过robot输入内容
     *
     * @param text
     */
    public void rbInput(String text) {
        RobotUtils.rbInput(text);
    }

    /**
     * 通过robot进行回车
     */
    public void rbEnter() {
        RobotUtils.rbEnter();
    }

    /*********************************等待**************************************/

    /**
     * @param second 输入秒数，可以是2.3
     */
    public boolean halt(String second) {
        try {
            float time = Float.parseFloat(second);
            int millis = (int) (time * 1000);
            Thread.sleep(millis);
            return true;
        } catch (Exception e) {
            log.error("等待出现问题，多半是数字转换格式有问题", e.fillInStackTrace());
            return false;
        }
    }


    /*****************************截图操作***************************************/
    /**
     * 截取整个浏览器的图片
     *
     * @param fileName
     */
    public void takeScreenShot(String fileName) {
        try {
            fileName += dateStr("MMdd HH：mm");
            log.info("正在截图。。。。。。。");
            File screenshotAs = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
            File picFile = new File("log/pic/" + fileName + ".png");
            Files.copy(screenshotAs, picFile);
        } catch (IOException e) {
            log.error("截图失败", e.fillInStackTrace());
        }
    }

    /**
     * 截取某个元素的图片。
     *
     * @param xpath
     * @param fileName
     */
    public void takeScreenShot(String xpath, String fileName) {
        try {
            fileName += dateStr("MMdd HH：mm");
            log.info("正在截图。。。。。。。");
            File screenshotAs = driver.findElement(By.xpath(xpath)).getScreenshotAs(OutputType.FILE);
            File picFile = new File("log/pic/" + fileName + ".png");
            Files.copy(screenshotAs, picFile);
        } catch (IOException e) {
            log.error("截图失败", e.fillInStackTrace());
        }
    }

    /******************************特殊方法******************************************/
    /**
     * 定位元素然后对元素执行js脚本操作，只需要输入最后的动作内容就行。
     *
     * @param jsAction 比如 click()   比如 innerText=""
     * @param xpath
     */
    public boolean runJsWithEle(String jsAction, String xpath) {
        try {
            JavascriptExecutor runner = (JavascriptExecutor) (driver);
            WebElement element = driver.findElement(By.xpath(xpath));
            runner.executeScript(" arguments[0]." + jsAction, element);
            return true;
        } catch (Exception e) {
            log.error("执行js脚本失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 可以得到js语句的执行结果，注意在js语句中加上return
     *
     * @param jsAction
     * @param xpath
     * @return
     */
    public String getJsResult(String jsAction, String xpath) {
        try {
            JavascriptExecutor runner = (JavascriptExecutor) (driver);
            WebElement element = driver.findElement(By.xpath(xpath));
            String result = runner.executeScript("return arguments[0]." + jsAction, element).toString();
            return result;
        } catch (Exception e) {
            AutoTools.log.info("执行js语句失败");
            return "执行js语句失败。";
        }
    }

    /**
     * 执行纯js语句。
     *
     * @param script
     */
    public boolean runJsScript(String script) {
        try {
            JavascriptExecutor runner = (JavascriptExecutor) (driver);
            runner.executeScript(script);
            log.info("执行js脚本：" + script);
            return true;
        } catch (Exception e) {
            log.error("执行js脚本失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 完成悬停到元素的操作
     *
     * @param xpath
     */
    public boolean hover(String xpath) {
        try {
            Actions actions = new Actions(driver);
            actions.moveToElement(driver.findElement(By.xpath(xpath))).perform();
            return true;
        } catch (Exception e) {
            AutoTools.log.error("悬停到指定元素" + xpath + "失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 对select元素进行操作，支持text和value两种方式
     * 需要在前面加上  {text} 或者{value}来表示使用哪种方式。
     * 服饰    {text}服饰
     *
     * @param xpath
     * @param value
     */
    public boolean select(String xpath, String value) {
        //把一个元素转为select对象。
        try {
            Select select = new Select(driver.findElement(By.xpath(xpath)));
            if (value.startsWith("{value}")) {
                select.selectByValue(value.substring(7));
                AutoTools.log.info(value.substring(7));
            } else if (value.startsWith("{text}")) {
                select.selectByVisibleText(value.substring(6));
            } else {
                //默认用text
                select.selectByVisibleText(value);
            }
            return true;
        } catch (Exception e) {
            AutoTools.log.error("定位select元素进行操作，失败。", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 单独使用text选择的方法。
     *
     * @param xpath
     * @param text
     */
    public boolean selectByText(String xpath, String text) {
        try {
            Select select = new Select(driver.findElement(By.xpath(xpath)));
            select.selectByVisibleText(text);
            return true;
        } catch (Exception e) {
            log.error("通过文本操作select元素失败", e.fillInStackTrace());
            return false;
        }
    }


    /**
     * 切换窗口的方法:
     * 通过页面的标题来进行切换(也可以用页面中是否存在某个元素进行判断。)
     * 找到每个句柄，切换到对应的窗口，获取标题，如果是需要的标题，就不再切换了，已经切换到需要的了。
     */
    public boolean switchWindow(String title) {
        try {
            Set<String> windowHandles = driver.getWindowHandles();
            for (String windowHandle : windowHandles) {
                //            找到每个句柄，切换到对应的窗口
                driver.switchTo().window(windowHandle);
                //如果包含的话，就已经找到了，不继续切换
                if (driver.getTitle().contains(title)) {
                    break;
                }
            }
            return true;
        } catch (Exception e) {
            log.error("切换到标题为" + title + "的窗口失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 方法有局限性，必须保持操作窗口只剩一个。
     */
    public boolean closeOldThenToNew() {
        try {
            //先获取旧窗口的句柄
            String oldHandle = driver.getWindowHandle();
            //关闭当前所在的旧窗口
            driver.close();
            Set<String> windowHandles = driver.getWindowHandles();
            //不是旧的窗口的就是新的。
            for (String windowHandle : windowHandles) {
                if (!windowHandle.equals(oldHandle)) {
                    driver.switchTo().window(windowHandle);
                    break;
                }
            }
            return true;
        } catch (Exception e) {
            log.error("关闭旧窗口后切换到新窗口失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 通过序号来进行窗口切换，利用set存储实际上用了hash排序，转成list使用序号。
     *
     * @param index 输入的序号，注意是自然序号 从1开始算。
     */
    public boolean switchWindowByIndex(String index) {
        try {
            Set<String> windowHandles = driver.getWindowHandles();
            //直接将set的值转为list
            List<String> handleList = new ArrayList<>(windowHandles);

            int i = Integer.parseInt(index);
            //将序号转成下标。
            driver.switchTo().window(handleList.get(i - 1));
            return true;
        } catch (NumberFormatException exception) {
            log.error("通过序号切换窗口失败", exception.fillInStackTrace());
            return false;
        }
    }

    /**
     * 同时支持 xpath和 name以及id的输入
     * xpath必然以//开头， name和id 随意。
     *
     * @param locator
     */
    public boolean switchFrame(String locator) {
        try {
            //如果以//开头，是个xpath，就定位iframe元素再切换
            if (locator.startsWith("//")) {
                WebElement iframe = driver.findElement(By.xpath(locator));
                driver.switchTo().frame(iframe);
            } else {
                //否则就是个name或者id，直接切换
                driver.switchTo().frame(locator);
            }
            return true;
        } catch (Exception e) {
            log.error("切换Iframe失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 切换到最外层的iframe操作
     */
    public boolean switchTop() {
        try {
            driver.switchTo().defaultContent();
            return true;
        } catch (Exception e) {
            log.error("切换到html最外层失败", e.fillInStackTrace());
            return false;
        }
    }


    /**
     * 切换到alert点击确认。
     *
     * @return
     */
    public boolean acceptAlert() {
        try {
            Alert alert = driver.switchTo().alert();
            alert.accept();
            return true;
        } catch (Exception e) {
            log.error("操作alert弹窗失败，请检查是否存在alert", e.fillInStackTrace());
            return false;
        }
    }

    /*******************************断言***************************************/

    /**
     * 断言元素中包含预期内容。
     *
     * @param locator        支持xpath 、css 、id 3种格式，但注意id中不要出现特殊字符
     * @param expectedResult
     * @return
     */
    public boolean assertEleText(String locator, String expectedResult) {
        locator = useParam(locator);
        expectedResult = useParam(expectedResult);
        try {
            //完成定位
            WebElement ele;
            if (locator.startsWith("/")) {
                ele = driver.findElement(By.xpath(locator));
            } else if (locator.startsWith("#") || locator.startsWith(".") || locator.contains("[")) {
                ele = driver.findElement(By.cssSelector(locator));
            } else {
                ele = driver.findElement(By.id(locator));
            }
            //判断元素内容是否包含指定结果
            if (ele.getText().contains(expectedResult)) {
                AutoTools.log.info("测试成功");
                return true;
            } else {
                AutoTools.log.info("测试失败预期的结果是" + expectedResult + "实际结果是" + ele.getText());

                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            AutoTools.log.info("测试失败，定位失败了");
            return false;
        }

    }

    /**
     * sql语句能够查到结果就是测试通过。
     *
     * @return
     */
    public boolean assertDbExists(String sql) {
        sql = useParam(sql);
        MysqlUtils mysql = new MysqlUtils();
        List<Map<String, String>> maps = mysql.queryDbData(sql);
        System.out.println("查询的结果是：" + maps);
        if (maps.size() > 0) {
            if (maps.get(0).get("error") != null && maps.get(0).get("error").equals("查询数据失败")) {
                AutoTools.log.error("测试失败，查询过程出错了");
                return false;
            }
            AutoTools.log.info("查询有数据，测试成功");
            AutoTools.log.info("数据是" + maps);
            return true;
        } else {
            AutoTools.log.info("没查到数据，测试失败");
            return false;
        }
    }

    /**
     * 关闭浏览器
     */
    public boolean closeBrowser() {
        try {
            driver.quit();
            return true;
        } catch (Exception e) {
            log.error("关闭浏览器出错", e.fillInStackTrace());
            return false;
        }
    }


}
